import { reaction, makeAutoObservable, toJS } from 'mobx';
import { v4 as uuidv4 } from 'uuid';
import naturalCompare from 'string-natural-compare';
import TestCase from '@/popup/models/TestCase';
import Suite from '@/popup/models/Suite';
import { VERSIONS } from '@/popup/IO/migrate';

export default class ProjectStore {
    id = uuidv4();

    modified = false;

    name = '';

    url = '';

    plugins = [];

    _tests = [];

    _suites = [];

    _urls = [];

    version = VERSIONS[VERSIONS.length - 1];

    constructor(name = 'Untitled Project') {
        this.name = name;
        this.changedTestDisposer = reaction(
            () => this._tests.find(({ modified }) => modified),
            (modified) => {
                if (modified) this.setModified(true);
            },
        );
        this.changeTestsDisposer = reaction(
            () => this._tests.length,
            () => this.setModified(true),
        );
        this.changedSuiteDisposer = reaction(
            () => this._suites.find(({ modified }) => modified),
            (modified) => {
                if (modified) this.setModified(true);
            },
        );
        this.changeSuitesDisposer = reaction(
            () => this._suites.length,
            () => this.setModified(true),
        );
        this.toJS = this.toJS.bind(this);
        this.dispose = this.dispose.bind(this);

        makeAutoObservable(this, {}, { autoBind: true });
    }

    get suites() {
        return this._suites.slice().sort((s1, s2) => naturalCompare(s1.name, s2.name));
    }

    get tests() {
        return this._tests.slice().sort((t1, t2) => naturalCompare(t1.name, t2.name));
    }

    get urls() {
        return this._urls.slice().sort();
    }

    setUrl(url) {
        this.url = url;
        this.setModified(true);
    }

    addUrl(urlToAdd) {
        if (urlToAdd) {
            const url = new URL(urlToAdd).href;
            if (!this._urls.find((u) => u === url)) {
                this._urls.push(url);
                this.setModified(true);
            }
        }
    }

    addCurrentUrl() {
        this.addUrl(this.url);
    }

    changeName(name) {
        this.name = name.replace(/<[^>]*>/g, ''); // firefox adds unencoded html elements to the string, strip them
        this.setModified(true);
    }

    setModified(modified = true) {
        this.modified = modified;
    }

    createSuite(...argv) {
        const suite = new Suite(undefined, ...argv);
        this._suites.push(suite);

        return suite;
    }

    deleteSuite(suite) {
        this._suites.remove(suite);
    }

    createTestCase(...argv) {
        const test = new TestCase(undefined, ...argv);
        this.addTestCase(test);

        return test;
    }

    addTestCase(test) {
        if (!test || !(test instanceof TestCase)) {
            throw new Error(`Expected to receive TestCase instead received ${test ? test.constructor.name : test}`);
        } else {
            let foundNumber = 0;
            // handle duplicate names -> name (1)
            // by using the sorted array we can do it in one read of the array
            this.tests.forEach((t) => {
                if (t.name === (foundNumber ? `${test.name} (${foundNumber})` : test.name)) foundNumber++;
            });
            if (foundNumber) {
                test.name = `${test.name} (${foundNumber})`;
            }
            this._tests.push(test);
            return test;
        }
    }

    duplicateTestCase(test) {
        const test2 = test.export();
        delete test2.id;
        test2.commands.forEach((cmd) => {
            delete cmd.id;
        });
        const toBeAdded = TestCase.fromJS(test2);
        this.addTestCase(toBeAdded);
    }

    deleteTestCase(test) {
        if (!test || !(test instanceof TestCase)) {
            throw new Error(`Expected to receive TestCase instead received ${test ? test.constructor.name : test}`);
        } else {
            this.suites.forEach((suite) => {
                suite.removeTestCase(test);
            });
            this._tests.remove(test);
        }
    }

    registerPlugin(plugin) {
        const existsInPlugins = this.plugins.findIndex((p) => p.id === plugin.id);
        if (existsInPlugins !== -1) {
            this.plugins[existsInPlugins] = plugin;
        } else {
            this.plugins.push(plugin);
        }
    }

    saved() {
        this._tests.forEach((test) => {
            test.modified = false;
        });
        this._suites.forEach((test) => {
            test.modified = false;
        });
        this.setModified(false);
    }

    fromJS(jsRep) {
        this.name = jsRep.name;
        this.setUrl(jsRep.url);
        this._tests.replace(jsRep.tests.map(TestCase.fromJS));
        this._suites.replace(jsRep.suites.map((suite) => Suite.fromJS(suite, this.tests)));
        this._urls.clear();
        jsRep.urls.forEach((url) => {
            this.addUrl(url);
        });
        this.plugins.replace(jsRep.plugins);
        this.version = jsRep.version;
        this.id = jsRep.id || uuidv4();
        this.saved();
    }

    dispose() {
        this.changeTestsDisposer();
        this.changedTestDisposer();
        this.changeSuitesDisposer();
        this.changedSuiteDisposer();
    }

    toJS() {
        return toJS({
            id: this.id,
            version: this.version,
            name: this.name,
            url: this.url,
            tests: this._tests.map((t) => t.export()),
            suites: this._suites.map((s) => s.export()),
            urls: this._urls.slice(),
            plugins: this.plugins.slice(),
        });
    }
}
